package junior.util.excel.ext;

import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import junior.util.excel.ExcelFileType;
import junior.util.excel.Excels;
import junior.util.excel.exporter.ExcelExportConfig;
import junior.util.excel.exporter.ExcelExporter;
import junior.util.excel.parser.ExcelParseConfig;
import junior.util.excel.parser.ExcelParser;

/**
 * Excel导入导出到{@link InputStream}和{@link OutputStream}的工具。
 * <ul>
 * <li>Excel和List&lt;POJO&gt;之间导入导出
 * <li>POJO需要导入或导出的字段请使用@{@link ExcelField}注解
 * <li>导入支持XLS和XLSX，通过<code>isLegacyXLS</code>参数选择
 * <li>导出<b>只支持XLSX</b>一种格式
 * </ul>
 * 
 * 
 * @author Zhang Xiaoye
 * @date 2017年9月6日 下午5:08:17
 */
public final class ExcelUtil {
	
	private static final int EXPORT_MAX_ROWS = 1000;
	
	private ExcelUtil(){}
	
	/**
	 * 增加列名映射到导出配置
	 * 
	 * @param klass POJO类型，在需要导入或导出的字段请使用@{@link ExcelField}注解
	 * @param cfg 用户自定义配置，null表示默认配置{autoSizeColumn: false}
	 * @param nameConvertor @{@link ExcelField}注解中列名转换，null为不转换
	 * @return 增加了列名映射的配置
	 * @author Zhang Xiaoye
	 * @date 2017年9月6日 下午5:16:47
	 */
	public static ExcelExportConfig makeExportConfig(Class<?> klass, ExcelExportConfig cfg, ColumnNameConvertor nameConvertor){
		Objects.requireNonNull(klass, "参数klass: 集合元素类型不能为null。");
		if(cfg == null){
			cfg = new ExcelExportConfig().setAutoSizeColumn(false);
		}
		Set<String> existedFields = new HashSet<>(cfg.getColNameMap().keySet());
		for(Field field: recurFieldsOf(klass)){
			ExcelField ee = field.getAnnotation(ExcelField.class);
			if(ee != null && ee.isExport()){
				if(ee.value() == null || ee.value().length() < 1){
					throw new RuntimeException(String.format("列名不能为空，请检查 %s.%s 上的ExcelExport注解。", klass.getName(), field.getName()));
				}
				if(! existedFields.contains(field.getName())){
					cfg.mapColName(field.getName(), nameConvertor != null? nameConvertor.apply(ee.value()): ee.value());
					existedFields.add(field.getName());
				}
			}
		}
		return cfg;
	}
	
	/**
	 * 请见 {@link #doExport(List, Class, OutputStream, ExcelExportConfig, ColumnNameConvertor)}
	 */
	public static <T> void doExport(List<? extends T> list, Class<T> klass, OutputStream stream){
		doExport(list, klass, stream, null, null);
	}
	
	/**
	 * 请见 {@link #doExport(List, Class, OutputStream, ExcelExportConfig, ColumnNameConvertor)}
	 */
	public static <T> void doExport(List<? extends T> list, Class<T> klass, OutputStream stream, ExcelExportConfig cfg){
		doExport(list, klass, stream, cfg, null);
	}
	
	/**
	 * 请见 {@link #doExport(List, Class, OutputStream, ExcelExportConfig, ColumnNameConvertor)}
	 */
	public static <T> void doExport(List<? extends T> list, Class<T> klass, OutputStream stream, ColumnNameConvertor nameConvertor){
		doExport(list, klass, stream, null, nameConvertor);
	}
	
	/**
	 * 导出到{@link OutputStream}
	 * 
	 * @param list 要导出的POJO列表
	 * @param klass POJO类型，在需要导入或导出的字段请使用@{@link ExcelField}注解
	 * @param stream 写入Excel文件的流
	 * @param cfg 用户自定义配置，null表示默认配置{autoSizeColumn: false}
	 * @param nameConvertor @{@link ExcelField}注解中列名转换，null为不转换
	 * @param <T> POJO类型
	 * @author Zhang Xiaoye
	 * @date 2017年9月6日 下午5:20:36
	 */
	public static <T> void doExport(List<? extends T> list, Class<T> klass, OutputStream stream, ExcelExportConfig cfg, ColumnNameConvertor nameConvertor){
		Objects.requireNonNull(klass, "参数stream: 输出流不能为null");
		cfg = makeExportConfig(klass, cfg, nameConvertor);
		
		ExcelExporter exporter = Excels.newExcelExporterForBigData(EXPORT_MAX_ROWS);
		try {
			exporter.setSheetData(list, cfg).export(stream);
		} catch (Exception e) {
			throw new RuntimeException("由以下异常导致Excel导出失败", e);
		}
	}
	
	/**
	 * 增加列名映射到导入配置
	 * 
	 * @param klass POJO类型，在需要导入或导出的字段请使用@{@link ExcelField}注解
	 * @param cfg 用户自定义配置，null表示默认配置{autoMapColName: false, includeSheetIndexes: [0]}
	 * @param nameConvertor @{@link ExcelField}注解中列名转换，null为不转换
	 * @return 增加了列名映射的配置
	 * @author Zhang Xiaoye
	 * @date 2017年9月6日 下午5:23:21
	 */
	public static ExcelParseConfig makeImportConfig(Class<?> klass, ExcelParseConfig cfg, ColumnNameConvertor nameConvertor){
		Objects.requireNonNull(klass, "参数klass: 集合元素类型不能为null。");
		if(cfg == null){
			cfg = new ExcelParseConfig().setAutoMapColName(false).withSheetIndexes(0);
		}
		Set<String> existedFields = new HashSet<>(cfg.getColNameMap().values());
		for(Field field: recurFieldsOf(klass)){
			ExcelField ee = field.getAnnotation(ExcelField.class);
			if(ee != null && ee.isImport()){
				if(ee.value() == null || ee.value().length() < 1){
					throw new RuntimeException(String.format("列名不能为空，请检查 %s.%s 上的ExcelImport注解。", klass.getName(), field.getName()));
				}
				if(! existedFields.contains(field.getName())){
					cfg.mapColName(nameConvertor != null? nameConvertor.apply(ee.value()): ee.value(), field.getName());
					existedFields.add(field.getName());
				}
			}
		}
		return cfg;
	}
	
	/**
	 * 请见{@link #doImport(Class, boolean, InputStream, ExcelParseConfig, ColumnNameConvertor)}
	 */
	public static <T> List<T> doImport(Class<T> klass, boolean isLegacyXLS, InputStream stream){
		return doImport(klass, isLegacyXLS, stream, null, null);
	}
	
	/**
	 * 请见{@link #doImport(Class, boolean, InputStream, ExcelParseConfig, ColumnNameConvertor)}
	 */
	public static <T> List<T> doImport(Class<T> klass, boolean isLegacyXLS, InputStream stream, ExcelParseConfig cfg){
		return doImport(klass, isLegacyXLS, stream, cfg, null);
	}
	
	/**
	 * 请见{@link #doImport(Class, boolean, InputStream, ExcelParseConfig, ColumnNameConvertor)}
	 */
	public static <T> List<T> doImport(Class<T> klass, boolean isLegacyXLS, InputStream stream, ColumnNameConvertor nameConvertor){
		return doImport(klass, isLegacyXLS, stream, null, nameConvertor);
	}
	
	/**
	 * 从{@link InputStream}导入
	 * 
	 * @param klass POJO类型，在需要导入或导出的字段请使用@{@link ExcelField}注解
	 * @param isLegacyXLS true:XLS类型 false:XLSX类型
	 * @param stream 读取Excel文件的流
	 * @param cfg 用户自定义配置，null表示默认配置{autoMapColName: false, includeSheetIndexes: [0]}
	 * @param nameConvertor @{@link ExcelField}注解中列名转换，null为不转换
	 * @param <T> POJO类型
	 * @return 导入的POJO列表
	 * @author Zhang Xiaoye
	 * @date 2017年9月6日 下午5:25:20
	 */
	public static <T> List<T> doImport(Class<T> klass, boolean isLegacyXLS, InputStream stream, ExcelParseConfig cfg, ColumnNameConvertor nameConvertor){
		Objects.requireNonNull(klass, "参数stream: 输出流不能为null");
		cfg = makeImportConfig(klass, cfg, nameConvertor);
		cfg.setSuppressTypeCastError(true);
		
		try{
			ExcelParser parser = Excels.newExcelParser(stream, isLegacyXLS? ExcelFileType.XLS: ExcelFileType.XLSX);
			return (List<T>) parser.parse2Bean(cfg, klass);
		}catch(Exception e){
			throw new RuntimeException("由以下异常导致Excel导入失败", e);
		}
	}
	
	/**
	 * 递归获取全部声明的字段
	 * 
	 * @param type 类型
	 * @return 字段列表
	 * @throws NullPointerException 类型参数为null
	 * @author Zhang Xiaoye
	 * @date 2017年8月18日 下午10:17:44
	 */
	static List<Field> recurFieldsOf(Class<?> type){
		if(type == null || type.isInterface() || type.isPrimitive() || Objects.class.equals(type) || Void.class.equals(type)){
			return Collections.emptyList();
		}
		List<Field> list = new ArrayList<>();
		for(Field field: type.getDeclaredFields()){
			list.add(field);
		}
		List<Field> superFields = recurFieldsOf(type.getSuperclass());
		if(! superFields.isEmpty()){
			list.addAll(superFields);
		}
		return list;
	}

}
